import axios, { AxiosInstance, AxiosRequestConfig, ResponseType } from 'axios'
import cookie, { TOKEN_KEY } from './util.cookies'
import loading from '@/libs/loading'
import router from '@/router'
import store from '@/store'
import { Message } from 'element-ui'
import globalInterface from '@/common/global'
import cookies from './util.cookies'

interface HttpRequestConfig {
  method: string,
  url: string,
  params?: any | undefined,
  data?: any | undefined,
  isFile?: boolean,
  isFormData?: boolean,
  loading?: object,
  successMessage?: object | boolean,
  responseType?: ResponseType | undefined
}
const NEDD_LOGIN_CODES = ['pleaselogin', 'please_login', 'PleaseLogin']
class HttpRequest {
  /**
   * 网络请求基础url
   */
  baseURL: string | undefined

  /**
   * 网络请求基础url
   */
  timeout: number | string

  /**
   * 网络请求队列
   */
  queue: globalInterface.IObject

  /**
   * axios示例对象
   */
  instance: AxiosInstance
  constructor (baseURL?: string | undefined, timeout?: number | undefined | string) {
    this.queue = {}
    this.baseURL = baseURL || process.env.VUE_APP_API
    this.timeout = timeout || process.env.VUE_APP_REQ_TIME_OUT || 0// 请求超时时间
    this.instance = axios.create({
      baseURL: this.baseURL,
      timeout: +this.timeout
    })
    this.addInterceptors()
  }

  /**
   * 添加axios拦截器
   */
  addInterceptors () {
    this.instance.interceptors.request.use(
      this.reqInterceptor.bind(this),
      this.reqError.bind(this)
    )

    // 响应拦截器
    this.instance.interceptors.response.use(
      this.resInterceptor.bind(this),
      this.resError.bind(this)
    )
  }

  /**
   * 删除请求队列
   */
  destory (url: string) {
    delete this.queue[url]
  }

  reqInterceptor (config: any): any {
    // 在请求发送之前做一些处理
    const token = cookie.get(TOKEN_KEY)
    const headers = {
      'Content-Type': 'application/json; charset=utf-8;',
      Authorization: 'Bearer ' + token
    }

    if (config.isFile) {
      headers['Content-Type'] = 'multipart/form-data'
    }

    if (config.isFormData) {
      config.data = this.packParams(config.data)
    }
    if (config.loading) {
      loading.show(config)
    }
    Object.assign(config.headers, headers)
    this.queue[config.url] = true
    return config
  }

  reqError<T> (error: T): Promise<T> {
    console.error(error)
    return Promise.reject(error)
  }

  /**
   * 接口响应拦截
   * @param response
   * @returns
   */
  resInterceptor<T> (response: any): Promise<T> {
    if (response.config && response.config.loading) {
      loading.hide(response.config)
    }

    this.destory(response.config.url)
    // 登录失效时预览或下载文件，单独处理
    if (response.data instanceof Blob) {
      return this.readBlob<T>(response)
    } else {
      // dataAxios 是 axios 返回数据中的 data
      const dataAxios = response.data
      // 这个状态码是和后端约定的
      let { code } = dataAxios

      if (
        code !== undefined &&
        code !== null &&
        (typeof code).toLowerCase() === 'string'
      ) {
        code = code.toLowerCase()
      }
      if (code === undefined || code === 'success') {
        if (response.config && response.config.successMessage) {
          const options = {
            message: '操作成功...',
            type: 'success',
            showClose: false,
            offset: 30,
            duration: 3500
          }
          Object.assign(options, response.config.successMessage)
          ;(Message as any)(options)
        }
        // 如果没有 code 代表这不是项目后端开发的接口 比如可能是 D2Admin 请求最新版本
        return response
      }
      if (
        NEDD_LOGIN_CODES.includes(code) ||
        code === 'account_status_exception'
      ) {
        cookies.remove(TOKEN_KEY)
      }
      if (NEDD_LOGIN_CODES.includes(code)) {
        let currentRoutePath = window.location.href
        if (currentRoutePath.indexOf('#') > 0) {
          currentRoutePath = currentRoutePath.split('#')[1]
        }
        if (window.location.href.indexOf('/login') < 0) {
          router.replace({
            name: 'login',
            query: { redirect: currentRoutePath }
          })
          ;(Message as any).closeAll()
          this.errorCreate('登录过期，请重新登录')
        }
        return Promise.reject(response)
      }
      // hideErrToast - 是否进行错误提示
      if (
        (!NEDD_LOGIN_CODES.includes(code) ||
          window.location.href.indexOf('/login') > 0) &&
        !response.config.hideErrToast
      ) {
        this.errorCreate(`${dataAxios.message || '系统开小差了！'}`)
      }
      return Promise.reject(response)
    }
  }

  resError<T> (error: any): Promise<T> {
    if (error.config && error.config.loading) {
      loading.hide(error.config)
    }
    this.destory(error.config.url)
    if (error && error.response) {
      switch (error.response.status) {
        case 400:
          error.message = '请求错误'
          break
        case 401:
          error.message = '未授权，请登录'
          break
        case 403:
          error.message = '拒绝访问'
          break
        case 404:
          error.message = `请求地址出错: ${error.response.config.url}`
          break
        case 408:
          error.message = '请求超时'
          break
        case 500:
          error.message = '服务器内部错误'
          break
        case 501:
          error.message = '服务未实现'
          break
        case 502:
          error.message = '网关错误'
          break
        case 503:
          error.message = '服务不可用'
          break
        case 504:
          error.message = '网关超时'
          break
        case 505:
          error.message = 'HTTP版本不受支持'
          break
        default:
          break
      }
    }
    if (error.message === 'Network Error') {
      error.message = '网络错误'
    }
    this.errorLog(error)
    return Promise.reject(error)
  }

  /**
   * 组装接口请求数据--formData
   * 说明：所有object/array类型均只支持一层，如果数据过于复杂，请根据实际需求修改
   * @param {Object} data 需要组装的数据
   */
  packParams (data: object) {
    if (!data) {
      return {}
    }
    const formData = new FormData()
    for (const key in data) {
      const value = (data as globalInterface.IObject)[key]
      if (value instanceof Array) {
        value.forEach((elem, index):void => {
          if (typeof elem === 'object') {
            for (const zkey in elem) {
              formData.append(`${key}[${index}].${zkey}`, elem[zkey])
            }
          } else {
            formData.append(`${key}[${index}]`, elem)
          }
        })
      } else if (typeof value === 'object') {
        const obj = value
        if (obj instanceof File) {
          formData.append(`${key}`, obj)
        } else {
          for (const pItem in obj) {
            formData.append(`${key}.${pItem}`, obj[pItem])
          }
        }
      } else {
        formData.append(key, value)
      }
    }
    return formData
  }

  // 读取blob原始数据
  readBlob<T> (res: any) {
    return new Promise<T>((resolve, reject):void => {
      const reader = new FileReader()
      reader.onload = (e):void => {
        let resJson
        try {
          const target = e.target || {}
          resJson = JSON.parse((target as globalInterface.IObject).result)
          if (NEDD_LOGIN_CODES.includes(resJson.code)) {
            let currentRoutePath = window.location.href
            if (currentRoutePath.indexOf('#') > 0) {
              currentRoutePath = currentRoutePath.split('#')[1]
            }
            router.replace({
              name: 'login',
              query: { redirect: currentRoutePath }
            })
            this.errorLog({ message: '登录过期，请重新登录' })
          } else {
            resolve(res)
          }
        } catch (e) {
          resolve(res)
        }
      }
      reader.readAsText(res.data)
    })
  }

  errorCreate (msg: string) {
    const error = new Error(msg)
    this.errorLog(error)
    throw error
  }

  // 记录和显示错误
  errorLog (error: globalInterface.IObject) {
    // 添加到日志
    // store.dispatch('d2admin/log/push', {
    //   message: '数据请求异常',
    //   type: 'danger',
    //   meta: {
    //     error
    //   }
    // })
    // 打印到控制台
    if (process.env.NODE_ENV === 'development') {
      // util.log.danger('>>>>>> Error >>>>>>')
      console.log(error)
    }
    // 显示提示
    Message({
      message: error.message,
      type: 'error',
      duration: 5 * 1000
    })
  }
}

const httpReq = new HttpRequest()

function request (config: HttpRequestConfig) {
  return httpReq.instance({ ...config } as AxiosRequestConfig)
}

export default request
